#!/usr/bin/python
# vi: et sw=2
#
#

import pygtk
import gtk
import gobject

import math
import sys

from PulseGlib import *

# Class to storage sinks and sink_inputs
# and keep data updated
class AppletModel(gobject.GObject):

  __gsignals__ = {
    "data-changed":         (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                             [ ])
    #,
    #"volume-level-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
    #                         [gobject.TYPE_PYOBJECT]),
    #"volume-mute-changed":  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
    #                         [gobject.TYPE_PYOBJECT])
  }

  def __init__(self):
    gobject.GObject.__init__(self)
    self.sinks = []
    self.sink_inputs = []
    self.pulse   = None

    pass

    #
    # __init__ end
    #

  # FIXME This can be called only if the connection to pa is ready
  def set_pulse(self, pulse):
    self.pulse = pulse

    self.pulse.connect("sink-info", self.on_pulse_sink_info)
    self.pulse.connect("sink-input-info", self.on_pulse_sink_input_info)

    self.pulse.connect("sink-new", self.on_pulse_sink_new)
    self.pulse.connect("sink-input-new", self.on_pulse_sink_input_new)
    self.pulse.connect("sink-change", self.on_pulse_sink_change)
    self.pulse.connect("sink-input-change", self.on_pulse_sink_input_change)
    self.pulse.connect("sink-remove", self.on_pulse_sink_remove)
    self.pulse.connect("sink-input-remove", self.on_pulse_sink_input_remove)

    self.pulse.connect("context-success", self.on_pulse_context_success)

    # refresh data
    self.pulse.pa_context_get_sink_info_list()
    self.pulse.pa_context_get_sink_input_info_list()

    #
    # set_pulse end
    #

  # Mute
  #
  def sink_get_mute(self, index):
    sink = self.sinks[index]

    return sink.mute != 0

  def sink_input_get_mute(self, index):
    sink_input = self.sink_inputs[index]

    return sink_input.mute != 0

  def sink_set_mute(self, index, mute = True):
    sink = self.sinks[index]
    sink.mute = int(mute)
    self.pulse.pa_context_set_sink_mute_by_index(sink.index, mute)
    pass

  def sink_input_set_mute(self, index, mute = True):
    sink_input = self.sink_inputs[index]
    sink_input.mute = int(mute)
    self.pulse.pa_context_set_sink_input_mute(sink_input.index, mute)
    pass

  # Volume
  #
  def sink_get_avg_volume(self):
    if len(self.sinks) == 0:
      return 0

    vol = 0
    for s in self.sinks:
      vol = vol + self.pulse.pa_cvolume_max(s.volume)

    return math.ceil(float(vol) * 100 / (PA_VOLUME_NORM * len(self.sinks)))

  def sink_get_volume(self, index):
    vol = self.pulse.pa_cvolume_max(self.sinks[index].volume)
    return math.ceil(float(vol) * 100 / PA_VOLUME_NORM)

  def sink_input_get_volume(self, index):
    vol = self.pulse.pa_cvolume_max(self.sink_inputs[index].volume)
    return math.ceil(float(vol) * 100 / PA_VOLUME_NORM)
    #return self.pulse.pa_sw_volume_to_linear(vol)

  def sink_get_volume_dB(self, index):
    if self.sink_get_mute(index):
      return "-inf";

    vol = self.pulse.pa_cvolume_max(self.sinks[index].volume)
    return round(self.pulse.pa_sw_volume_to_dB(vol), 2)

  def sink_input_get_volume_dB(self, index):
    if self.sink_input_get_mute(index):
      return "-inf";

    vol = self.pulse.pa_cvolume_max(self.sink_inputs[index].volume)
    return round(self.pulse.pa_sw_volume_to_dB(vol), 2)

  def sink_get_volume_icon_name(self, index):
    return self._volume_icon_name(
             self.sink_get_volume(index),
             self.sink_get_mute(index)
           )

  def sink_input_get_volume_icon_name(self, index):
    return self._volume_icon_name(
             self.sink_input_get_volume(index),
             self.sink_input_get_mute(index)
           )

  def _volume_icon_name(self, volume, mute):
    if volume == 0 or mute:
      return "audio-volume-muted"
    elif volume < 34:
      return "audio-volume-low"
    elif volume < 67:
      return "audio-volume-medium"
    return "audio-volume-high"

  def sink_set_volume(self, index, vol):
    sink = self.sinks[index]

    pa_volume_t = int(math.floor(float(vol * PA_VOLUME_NORM) / 100))
    pa_cvolume = sink.volume

    # Scale to new volume
    self.pulse.pa_cvolume_scale(pa_cvolume, pa_volume_t)

    # this emits signal when it is ready
    self.pulse.pa_context_set_sink_volume_by_index(sink.index, pa_cvolume)

    #self.emit("data-changed")
    pass

  def sink_input_set_volume(self, index, vol):
    sink_input = self.sink_inputs[index]

    pa_volume_t = int(math.floor(float(vol * PA_VOLUME_NORM) / 100))
    pa_cvolume = sink_input.volume

    # Scale to new volume
    self.pulse.pa_cvolume_scale(pa_cvolume, pa_volume_t)

    # this emits signal when it is ready
    self.pulse.pa_context_set_sink_input_volume(sink_input.index, pa_cvolume)

    #self.emit("data-changed")
    pass

  # Name/desc
  #
  def sink_get_name(self, index):
    sink = self.sinks[index]
    return sink.name

  def sink_get_desc(self, index):
    sink = self.sinks[index]
    return sink.description

  def sink_input_get_name(self, index):
    sink_input = self.sink_inputs[index]
    return sink_input.name

  def sink_input_get_desc(self, i):
    return "FIXME get client name" #self.sink_inputs[i].description

  # Data manipulation
  #
  def sink_count(self):
    return len(self.sinks)

  def sink_input_count(self):
    return len(self.sink_inputs)

  #
  # PulseAudio events

  def on_pulse_context_success(self, pulse, success):
    if success == 0:
      # FIXME add some notification
      pass
    else:
      self.emit("data-changed")

    pass

    #
    # on_pulse_context_success end
    #

  def on_pulse_sink_info(self, pulse, pa_sink_info_p, eol):
    if eol != 0:
      self.emit("data-changed")
      return

    # Hard copy struct to memory
    pa_sink_info = PA_SINK_INFO()
    memmove(byref(pa_sink_info), pa_sink_info_p, sizeof(PA_SINK_INFO))

    # Copy strings
    pa_sink_info.name = str(pa_sink_info_p[0].name)
    pa_sink_info.description = str(pa_sink_info_p[0].description)
    pa_sink_info.monitor_source_name = \
                                str(pa_sink_info_p[0].monitor_source_name)
    pa_sink_info.driver = str(pa_sink_info_p[0].driver)

    # FIXME proplist needs also copied

    for sink_info in self.sinks:
      if pa_sink_info.index == sink_info.index:
        self.sinks.remove(sink_info)

    self.sinks.append(pa_sink_info)

    pass

    #
    # on_pulse_sink_info end
    #

  def on_pulse_sink_input_info(self, pulse, pa_sink_input_info_p, eol):
    if eol != 0:
      self.emit("data-changed")
      return

    # Hard copy struct to memory
    # and hope that Python releases it
    pa_sink_input_info = PA_SINK_INPUT_INFO()
    memmove(byref(pa_sink_input_info),
            pa_sink_input_info_p, sizeof(PA_SINK_INPUT_INFO))

    pa_sink_input_info.name = str(pa_sink_input_info_p[0].name)
    pa_sink_input_info.resample_method = \
                                str(pa_sink_input_info_p[0].resample_method)
    pa_sink_input_info.driver = str(pa_sink_input_info_p[0].driver)

    # FIXME proplist needs also copied

    for sink_input in self.sink_inputs:
      if pa_sink_input_info.index == sink_input.index:
        self.sink_inputs.remove(sink_input)

    self.sink_inputs.append(pa_sink_input_info)

    pass

    #
    # on_pulse_sink_input_info end
    #

    #
    # FIXME on_pulse sink[_input]_* is done by update all
    #       it would be better update only changed data
    #
  def on_pulse_sink_new(self, pulse, index):
    # Refresh data
    self.pulse.pa_context_get_sink_info_list()
    pass

    #
    # on_pulse_sink_new end
    #

  def on_pulse_sink_input_new(self, pulse, index):
    # Refresh data
    self.pulse.pa_context_get_sink_input_info_list()
    pass

    #
    # on_pulse_sink_input_new end
    #

  def on_pulse_sink_change(self, pulse, index):
    # Refresh data
    self.pulse.pa_context_get_sink_info_list()
    pass

    #
    # on_pulse_sink_change end
    #

  def on_pulse_sink_input_change(self, pulse, index):
    # Refresh data
    self.pulse.pa_context_get_sink_input_info_list()
    pass

    #
    # on_pulse_sink_input_change end
    #

  def on_pulse_sink_remove(self, pulse, index):
    for pa_sink in self.sinks:
      if pa_sink.index == index:
        self.sinks.remove(pa_sink)

    # Refresh data
    self.pulse.pa_context_get_sink_info_list()
    pass

    #
    # on_pulse_sink_remove end
    #

  def on_pulse_sink_input_remove(self, pulse, index):
    for pa_sink_input in self.sink_inputs:
      if pa_sink_input.index == index:
        self.sink_inputs.remove(pa_sink_input)

    # Refresh data
    self.pulse.pa_context_get_sink_input_info_list()
    pass

    #
    # on_pulse_sink_input_remove end
    #

  pass

gobject.type_register(AppletModel)
